package com.beerandjesus.sportofkings.handicapper.strategy;

import java.math.BigDecimal;

import org.apache.commons.lang.StringUtils;
import com.beerandjesus.sportofkings.handicapper.model.MixedNumeral;

public class RacingCalculator {
	
	private static final String FURLONG = " Furlong";
	private static final String F = "f";
	private static final String MILE = " Mile";
	private static final String M = "M";
	private static final String FURLONGS = " Furlongs";
	private static final BigDecimal ZERO = new BigDecimal(0);
	private static final BigDecimal ONE_FURLONG_IN_YARDS = new BigDecimal(220);
	private static final int SEVEN_FURLONGS_IN_YARDS = 1540;
	private static final BigDecimal ONE_MILE_IN_YARDS = new BigDecimal(1760);


    /**
	 * Determine whether a given distance in yards is a sprint
	 * or route
	 * @param yards
	 * @return
	 */
	public boolean isRoute(String yards) {
		BigDecimal dist;
		try {
			dist = new BigDecimal(yards);
		} catch (Exception e) {
			return false;
		}
		if (dist.abs().intValue() >= SEVEN_FURLONGS_IN_YARDS) {
			return true;
		}
		return false;
	}
	
	/**
	 * Convert yards to furlongs or miles, displayed as a
	 * fraction (e.g., 6 1/2 Furlongs).
	 * @param yards
	 * @return The distance in furlongs or miles.
	 */
	public String distanceFromYards(String yards) {
		return toFraction(yards, FURLONGS, MILE, FURLONG);
	
	}
	
	/**
	 * Convert yards to furlongs or miles, displayed as a
	 * fraction, using short notation (e.g., 6 1/2f).
	 * @param yards
	 * @return
	 */
	public String distanceFromYardsShortNotation(String yards) {
		return toFraction(yards, F, M, F);
		
	}

	private String toFraction(String yards, String furlongsMeasure,
			String mileMeasure, String furlongMeasure) {
		if (StringUtils.isEmpty(yards)) {
			throw new IllegalArgumentException("String cannot be empty");
		}
		String measure = furlongsMeasure;
		MixedNumeral fraction;
		BigDecimal d = new BigDecimal(yards.trim()).abs();
		try {
			if (d.compareTo(ONE_MILE_IN_YARDS) >= 0) {
				// Miles
				BigDecimal div = d.divide(ONE_MILE_IN_YARDS);
				fraction = toFraction(div);
				measure = mileMeasure;
			} else {
				// Furlongs
				BigDecimal div = d.divide(ONE_FURLONG_IN_YARDS);
				fraction = toFraction(div);
				if (fraction.getInteger() <= 1) {
					measure = furlongMeasure;
				}
			}
		} catch (ArithmeticException e) {
			return yards + " yards";
		}
        return fraction.toString() + measure;
	}

	/**
	 * Convert a number to a fraction, for example, 1.0625 = 1 1/16.
	 * @param d The number to convert
	 * @return A MixedNumeral for the decimal reduced to its
     *         lowest common denomenator.
     *
	 */
	public MixedNumeral toFraction(BigDecimal d) {
		String str = d.toPlainString();
		String decimal = str.substring(str.indexOf(".")+1, str.length());
		String bottom = "1";
		for (int j = 1; j <= decimal.length(); j++) {
			bottom += "0";
		}
		if (str.indexOf(".") == -1) {
			// No fraction
            return new MixedNumeral(d.intValue(), 0, 0);
		}
		BigDecimal[] frct = new BigDecimal[3];
		frct[0]	= new BigDecimal(str.substring(0, str.indexOf(".")));
		frct[1] = new BigDecimal(decimal);
		frct[2] = new BigDecimal(bottom);
		BigDecimal divisor;
		BigDecimal[] resTop;
		BigDecimal[] resBot;
		for (int i = 9; i > 1; i--) {
			divisor = new BigDecimal(i);
			resTop = frct[1].divideAndRemainder(divisor);
			resBot = frct[2].divideAndRemainder(divisor);
			if (resTop[1].compareTo(ZERO) == 0 &&
					resBot[1].compareTo(ZERO) == 0) {
				frct[1] = resTop[0];
				frct[2] = resBot[0];
				i = 10;
				continue;
			}
		}

        int numerator = frct[1].intValue();
        int denomenator = frct[2].intValue();
        if (numerator == 0 || denomenator == 0) {
            numerator = 0;
            denomenator = 0;
        }
        return new MixedNumeral(frct[0].intValue(), numerator, denomenator);
	}

}
